﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using Sinacor.Infra.UI.Controls.SmartClient;
using System.Collections;
using System.Windows.Controls.Primitives;
using System.Linq;

namespace Sinacor.Infra.UI.Controls.SmartClient
{
    public class SinacorDragDrop : ContentControl
    {
        public SinacorDragDrop()
        {
            this.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(PreviewMouseLeftButtonDownHandler);
            this.PreviewMouseMove += new MouseEventHandler(PreviewMouseMoveHandler);
            this.PreviewMouseLeftButtonUp += new MouseButtonEventHandler(PreviewMouseLeftButtonUpHandler);
            
            this.PreviewDragOver += new DragEventHandler(PreviewDragOverHandler);
            
            //this.DragLeave += new DragEventHandler(DragLeaveHandler);
            //this.QueryContinueDrag += new QueryContinueDragEventHandler(QueryContinueDragHandler);

            this.AllowDrop = true;
        }

        SinacorDragAdorner _adorner = null;
        AdornerLayer _layer;
        Point _startPoint;
        //bool _dragHasLeftScope = false;
        //bool _isDragging;

        #region IsDragSource
        public static readonly DependencyProperty IsDragSourceProperty = DependencyProperty.RegisterAttached("IsDragSource", typeof(bool), typeof(SinacorDragDrop), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits));

        public static void SetIsDragSource(DependencyObject d, bool value)
        {
            d.SetValue(SinacorDragDrop.IsDragSourceProperty, value);
        }
        public static bool GetIsDragSource(DependencyObject d)
        {
            return (bool)d.GetValue(SinacorDragDrop.IsDragSourceProperty);
        }
        #endregion

        #region IsDropTarget
        public static readonly DependencyProperty IsDropTargetProperty = DependencyProperty.RegisterAttached("IsDropTarget", typeof(bool), typeof(SinacorDragDrop), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits));

        public static void SetIsDropTarget(DependencyObject d, bool value)
        {
            d.SetValue(SinacorDragDrop.IsDropTargetProperty, value);
        }
        public static bool GetIsDropTarget(DependencyObject d)
        {
            return (bool)d.GetValue(SinacorDragDrop.IsDropTargetProperty);
        }
        #endregion

        #region IsDragOver
        private static readonly DependencyPropertyKey IsDragOverKey = DependencyProperty.RegisterAttachedReadOnly("IsDragOver", typeof(bool), typeof(SinacorDragDrop), new FrameworkPropertyMetadata(false));
        public static readonly DependencyProperty IsDragOverProperty = IsDragOverKey.DependencyProperty;

        internal static void SetIsDragOver(DependencyObject d, bool value)
        {
            d.SetValue(SinacorDragDrop.IsDragOverKey, value);
        }
        public static bool GetIsDragOver(DependencyObject d)
        {
            return (bool)d.GetValue(SinacorDragDrop.IsDragOverProperty);
        }
        #endregion

        #region IsDragging
        private static readonly DependencyPropertyKey IsDraggingKey = DependencyProperty.RegisterReadOnly("IsDragging", typeof(bool), typeof(SinacorDragDrop), new FrameworkPropertyMetadata(false));
        public static readonly DependencyProperty IsDraggingProperty = IsDraggingKey.DependencyProperty;

        public bool IsDragging
        {
            get { return (bool)GetValue(IsDraggingProperty); }
            private set { SetValue(IsDraggingKey, value); }
        }
        #endregion

        #region CurrentDragSource
        private static readonly DependencyPropertyKey CurrentDragSourceKey = DependencyProperty.RegisterReadOnly("CurrentDragSource", typeof(object), typeof(SinacorDragDrop), new FrameworkPropertyMetadata(null));
        public static readonly DependencyProperty CurrentDragSourceProperty = CurrentDragSourceKey.DependencyProperty;

        public object CurrentDragSource
        {
            get { return (object)GetValue(CurrentDragSourceProperty); }
            private set { SetValue(CurrentDragSourceKey, value); }
        }
        #endregion

        #region Mouse
        void PreviewMouseLeftButtonDownHandler(object sender, MouseButtonEventArgs e)
        {
            _startPoint = e.GetPosition(null);
        }

        void PreviewMouseMoveHandler(object sender, MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed && !this.IsDragging)
            {
                Point position = e.GetPosition(null);

                if (Math.Abs(position.X - _startPoint.X) > SystemParameters.MinimumHorizontalDragDistance ||
                    Math.Abs(position.Y - _startPoint.Y) > SystemParameters.MinimumVerticalDragDistance)
                {
                    StartDragInProcAdorner(e);
                }
            }
        }

        void PreviewMouseLeftButtonUpHandler(object sender, MouseButtonEventArgs e)
        {
            this.IsDragging = false;
        }
        #endregion


        #region Drag

        void PreviewDragOverHandler(object sender, DragEventArgs e)
        {
            if (_adorner != null)
            {
                _adorner.LeftOffset = e.GetPosition(this).X;
                _adorner.TopOffset = e.GetPosition(this).Y;
            }

            if (!SinacorDragDrop.GetIsDropTarget((DependencyObject)e.OriginalSource))
            {
                e.Effects = DragDropEffects.None;
                e.Handled = true;
            }
        }

        //void DragLeaveHandler(object sender, DragEventArgs e)
        //{
        //    Point p = e.GetPosition(this);
        //    Rect r = VisualTreeHelper.GetContentBounds(this);
        //    if (!r.IsEmpty && !r.Contains(p))
        //    {
        //        _dragHasLeftScope = true;
        //        e.Handled = true;
        //    }
        //}

        //void QueryContinueDragHandler(object sender, QueryContinueDragEventArgs e)
        //{
        //    if (this._dragHasLeftScope)
        //    {
        //        e.Action = DragAction.Cancel;
        //        e.Handled = true;
        //    }
        //}

        #endregion


        void StartDragInProcAdorner(MouseEventArgs e)
        {
            // Captura o elemento arrastado
            UIElement hitTestElement = GetDragElementFromHitTest(this, e);

            UIElement draggedElement = null;

            // Verifica se o elemento ou o pai é um IDragSource
            if (hitTestElement != null)
                draggedElement = hitTestElement is IDragSource ? hitTestElement : ControlHelper.FindParent((DependencyObject)hitTestElement, typeof(IDragSource)) as UIElement;

            // Verifica se elemento permite ser arrastado
            if (draggedElement != null && SinacorDragDrop.GetIsDragSource(draggedElement) && 
                 (
                   draggedElement is IItemContainerDragSource == false || 
                   draggedElement is IItemContainerDragSource == true && ((IItemContainerDragSource)draggedElement).IsDragging
                 )
               )
            {
                this.IsDragging = true;

                DataObject data = null;

                if (draggedElement is IItemContainerDragSource)
                {
                    // Captura os itens sendo arrastados
                    IItemsControlDragSource itemsControlDragSource = ((IItemContainerDragSource)draggedElement).GetItemsControl();
                    IList draggedItems = itemsControlDragSource.GetDraggedItems();

                    // Captura o ItemsControl do Container
                    ItemsControl itemsControl = ItemsControl.ItemsControlFromItemContainer(draggedElement);

                    // Ordena os itens na ordem que estão sendo exibidos
                    IEnumerable items = (from object i in itemsControl.Items where draggedItems.Contains(i) select i).ToList();

                    data = new DataObject(typeof(IItemsControlDragSource), itemsControlDragSource);
                    data.SetData(typeof(IEnumerable), items);

                    _adorner = new SinacorDragAdorner(this, itemsControl, items);

                    this.CurrentDragSource = itemsControlDragSource;
                }
                else
                {
                    data = new DataObject(typeof(IDragSource), draggedElement);

                    _adorner = new SinacorDragAdorner(this, (IDragSource)draggedElement);

                    this.CurrentDragSource = draggedElement;
                }

                 _layer = AdornerLayer.GetAdornerLayer(this);
                _layer.Add(_adorner);

                DragDropEffects de = System.Windows.DragDrop.DoDragDrop(this, data, DragDropEffects.Move);

                AdornerLayer.GetAdornerLayer(this).Remove(_adorner);
                _adorner = null;

                this.CurrentDragSource = null;
            }
        }

        private UIElement _hitTestElement = null;

        private UIElement GetDragElementFromHitTest(UIElement dragScopeContainer, MouseEventArgs args)
        {
            _hitTestElement = null;

            VisualTreeHelper.HitTest(dragScopeContainer,
                null,
                new HitTestResultCallback(HitTestResult),
                new PointHitTestParameters(args.GetPosition((IInputElement)dragScopeContainer)));

            return _hitTestElement;
        }

        private HitTestResultBehavior HitTestResult(HitTestResult result)
        {
            UIElement element = result.VisualHit as UIElement;

            if (element != null && element.IsVisible)
            {
                _hitTestElement = element;
                return HitTestResultBehavior.Stop;
            }

            return HitTestResultBehavior.Continue;
        }
    }
}